package com.company.user;

import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.lang.generator.SnowflakeGenerator;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTUtil;
import cn.hutool.jwt.signers.JWTSignerUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.company.api.UserServiceI;
import com.company.common.Constants;
import com.company.common.exception.AuthException;
import com.company.common.util.NameUtil;
import com.company.dto.MembersLoginCmd;
import com.company.dto.VisitorLoginCmd;
import com.company.dto.data.Identity;
import com.company.dto.data.LoginInfoDTO;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashMap;
import java.util.Objects;

import static com.company.common.ApplicationConstant.FRONT_JWT_EXTRA_USER_ID;
import static com.company.common.ApplicationConstant.FRONT_JWT_USERNAME;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,UserDO> implements UserServiceI {

    private final RefreshTokenMapper refreshTokenMapper;

    public UserServiceImpl(RefreshTokenMapper refreshTokenMapper) {
        this.refreshTokenMapper = refreshTokenMapper;
    }

    @Override
    public LoginInfoDTO loginMembers(MembersLoginCmd cmd) {
        return null;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public LoginInfoDTO loginVisitor(VisitorLoginCmd cmd) {
        UserDO userInfo = super.getOne(new QueryWrapper<UserDO>().lambda().eq(UserDO::getDeviceId, cmd.getDeviceId()));
        if (userInfo != null) {
            //证明该设备id已经注册过了
            //donothing


        }else {
            String password = RandomUtil.randomString(6);
            SnowflakeGenerator snowflakeGenerator = new SnowflakeGenerator();
            userInfo = UserDO.builder()
                    .id(snowflakeGenerator.next())
                    .password(password)
                    .nickName(NameUtil.getName())
                    .isGuest(true)
                    .deviceId(cmd.getDeviceId())
                    .isMember(false)
                    .userId(UUID.fastUUID().toString())
                    .userName(this.genGuestUserName()).build();
            super.save(userInfo);

        }
        RefreshTokenDO refreshTokenDO = refreshTokenMapper.selectOne(new QueryWrapper<RefreshTokenDO>().lambda().eq(RefreshTokenDO::getUserId, userInfo.getUserId()));
        HashMap<String, Object> map = new HashMap<>();
        long expireTime = DateUtil.current() + Constants.REFRESH_TOKEN_TIMEOUT_MILL_SECONDS;
        map.put("userId", userInfo.getUserId());
        map.put("expireTime",expireTime);
        String refreshToken= JWTUtil.createToken(map, JWTSignerUtil.hs256(Constants.REFRESH_TOKEN_SIGN_KEY.getBytes() ));
        if (Objects.isNull(refreshTokenDO)){
            refreshTokenDO = RefreshTokenDO.builder().token(refreshToken).userId(userInfo.getUserId()).expireTime(DateUtil.current() + Constants.REFRESH_TOKEN_TIMEOUT_MILL_SECONDS).build();
            refreshTokenMapper.insert(refreshTokenDO);
        }else {
            refreshTokenDO.setToken(refreshToken);
            refreshTokenDO.setExpireTime((DateUtil.current() + Constants.REFRESH_TOKEN_TIMEOUT_MILL_SECONDS));
            refreshTokenMapper.updateById(refreshTokenDO);
        }

        StpUtil.login(userInfo.getUserId(), SaLoginModel.create()
                .setExtra(FRONT_JWT_USERNAME, userInfo.getUserName())
                .setExtra(FRONT_JWT_EXTRA_USER_ID, userInfo.getId()));

        Identity identity = Identity.builder().vip(false).userId(userInfo.getUserId())
                .createTime(DateUtil.formatLocalDateTime(userInfo.getCreateTime()))
                .vipExpireTime(this.getMemberExpireTime(userInfo.getMemberExpireTime()))
                .nickname(userInfo.getNickName()).build();

        return LoginInfoDTO.builder().refreshToken(LoginInfoDTO.RefreshToken.builder().value(refreshToken).expireTime(expireTime).build()).accessToken(StpUtil.getTokenValue()).identity(identity).baseUserId(userInfo.getUserId()).build();
    }

    private Long getMemberExpireTime(Long memberExpireTime){
        if (Objects.isNull(memberExpireTime)){
            return -28800000L;
        }else {
            return memberExpireTime;
        }
    }

    private String genGuestUserName(){
        return "guest_"+ RandomUtil.randomString(6);

    }

    @Override
    public void getMobileVerifyCode(String mobile) {

    }

    @Override
    public void getEmailVerifyCode(String email) {

    }

    @Override
    public LoginInfoDTO refreshToken(String refreshToken) {
        boolean verify = JWTUtil.verify(refreshToken, JWTSignerUtil.hs256(Constants.REFRESH_TOKEN_SIGN_KEY.getBytes()));
        if (!verify) {
            throw new AuthException("Invalid refresh token");
        }
        JWT jwt = JWTUtil.parseToken(refreshToken);
        String userId = jwt.getPayloads().getStr("userId");
        Long expireTime = jwt.getPayloads().getLong("expireTime");
        if (DateUtil.current()>expireTime){
            throw new AuthException("Expired refresh token");
        }
        UserDO userInfo = super.getOne(new QueryWrapper<UserDO>().lambda().eq(UserDO::getUserId, userId));
        StpUtil.login(userInfo.getUserId(), SaLoginModel.create()
                .setExtra(FRONT_JWT_USERNAME, userInfo.getUserName())
                .setExtra(FRONT_JWT_EXTRA_USER_ID, userInfo.getId()));
        Identity identity = Identity.builder().vip(userInfo.getIsMember()).userId(userInfo.getUserId())
                .createTime(DateUtil.formatLocalDateTime(userInfo.getCreateTime()))
                .vipExpireTime(this.getMemberExpireTime(userInfo.getMemberExpireTime()))
                .nickname(userInfo.getNickName()).build();
        return LoginInfoDTO.builder().refreshToken(LoginInfoDTO.RefreshToken.builder().value(refreshToken).expireTime(expireTime).build()).accessToken(StpUtil.getTokenValue()).identity(identity).baseUserId(userInfo.getUserId()).build();
    }
}
